﻿/*
** Xin YUAN, 2023, BSD (2)
*/

////////////////////////////////////////////////////////////////////////////////

#include <Winsock2.h>
#include <Ws2tcpip.h>
#include <Mswsock.h>

#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "Mswsock.lib")

////////////////////////////////////////////////////////////////////////////////

//_w_socket_support

//MAKE_WINSOCK_VERSION
/*! \brief A macro for making winsock version.

A macro for making winsock version.
\param Major, Minor [in] Versions.
*/
#define MAKE_WINSOCK_VERSION(Major, Minor)  MAKEWORD((Major), (Minor))

/*! \brief A class for socket support.

A class for socket support.
\note Use a global variable of this class in main function.
*/

class _w_socket_support
{
public:
	/*! \brief Constructor.

	Constructor.
	*/
	_w_socket_support() noexcept : m_bInitialized(FALSE)
	{
		::ZeroMemory(&m_stData, sizeof(WSADATA));
	}
	_w_socket_support(const _w_socket_support&) = delete;
	/*! \brief Destructor.

	Destructor.
	*/
	~_w_socket_support() noexcept
	{
		if( m_bInitialized )
			Stop();
	}

	_w_socket_support& operator=(const _w_socket_support&) = delete;

	/*! \brief Initialize the socket DLL.

	Initialize the socket DLL.
	\param wRequestVersion [in] The request version. Use MAKE_WINSOCK_VERSION macro.
	\return A HRESULT value.
	*/
	HRESULT Initialize(WORD wRequestVersion) noexcept
	{
		assert( !m_bInitialized );

		//start up
		int err = ::WSAStartup(wRequestVersion, &m_stData);
		if( err != 0 )
			return HRESULT_FROM_WIN32(err);

		// Confirm that the WinSock DLL supports the request version.
		if( m_stData.wVersion != wRequestVersion ) {
			int ret = ::WSACleanup();
			(void)ret;
			assert( ret == 0 );  //SOCKET_ERROR, ::WSAGetLastError()
			return E_FAIL;
		}
		m_bInitialized = TRUE;

		return S_OK;
	}

	/*! \brief Stop the socket DLL.

	Stop the socket DLL.
	*/
	void Stop() noexcept
	{
		assert( m_bInitialized );
		m_bInitialized = FALSE;
		int ret = ::WSACleanup();
		(void)ret;
		assert( ret == 0 );  //SOCKET_ERROR, ::WSAGetLastError()
	}

	/*! \brief Check if the socket DLL is started.

	Check if the socket DLL is started.
	\return TRUE for started, FALSE for otherwise.
	*/
	BOOL IsStarted() const noexcept
	{ return m_bInitialized; }
	/*! \brief Get WSA data.

	Get WSA data.
	\return A reference to WSADATA.
	*/
	const WSADATA& GetWsaData() const noexcept
	{
		return m_stData;
	}

protected:
	WSADATA  m_stData;        //!< WSA data
	BOOL     m_bInitialized;  //!< Initialized flag
};

// WSAEvent

/*! \brief A class for WSA event.

A class for WSA event.
*/
class WSAEvent
{
public:
	/*! \brief Constructor.

	Constructor.
	*/
	explicit WSAEvent(WSAEVENT hEvent = WSA_INVALID_EVENT) noexcept : m_hEvent(hEvent)
	{
	}
	WSAEvent(const WSAEvent&) = delete;
	WSAEvent(WSAEvent&& src) noexcept : m_hEvent(src.m_hEvent)
	{
		src.m_hEvent = WSA_INVALID_EVENT;
	}
	/*! \brief Destructor.

	Destructor.
	*/
	~WSAEvent() noexcept
	{
		Close();
	}

	WSAEvent& operator=(const WSAEvent&) = delete;
	WSAEvent& operator=(WSAEvent&& src) noexcept
	{
		if ( this != &src ) {
			if ( m_hEvent == src.m_hEvent ) {
				assert( m_hEvent == WSA_INVALID_EVENT );
			}
			else {
				Close();
				m_hEvent = src.m_hEvent;
				src.m_hEvent = WSA_INVALID_EVENT;
			}
		}
		return *this;
	}

	/*! \brief Close the event.

	Close the event.
	*/
	void Close() noexcept
	{
		if( m_hEvent != WSA_INVALID_EVENT ) {
			BOOL bRet = ::WSACloseEvent(m_hEvent);
			(void)bRet;
			assert( bRet );  //FALSE, ::WSAGetLastError()
			m_hEvent = WSA_INVALID_EVENT;
		}
	}

	/*! \brief Default cast operator.

	Default cast operator.
	*/
	operator WSAEVENT() const noexcept
	{
		return m_hEvent;
	}
	/*! \brief Check if the event is valid.

	Check if the event is valid.
	\return true for valid, false for invalid.
	*/
	bool IsValid() const noexcept
	{
		return m_hEvent != WSA_INVALID_EVENT;
	}

	/*! \brief Attach a handle.

	Attach a handle.
	\param hEvent [in] A handle.
	*/
	void Attach(WSAEVENT hEvent) noexcept
	{
		if( hEvent != m_hEvent ) {
			Close();
			m_hEvent = hEvent;
		}
	}
	/*! \brief Detach a handle.

	Detach a handle.
	\return An event handle.
	*/
	WSAEVENT Detach() noexcept
	{
		WSAEVENT hEvent = m_hEvent;
		m_hEvent = WSA_INVALID_EVENT;
		return hEvent;
	}

	/*! \brief Create event.

	Create event.
	\return TRUE for succeeded, FALSE for failed.
	*/
	BOOL Create() noexcept
	{
		assert( !IsValid() );
		m_hEvent = ::WSACreateEvent();
		return m_hEvent != WSA_INVALID_EVENT;  //FALSE, ::WSAGetLastError()
	}

	/*! \brief Reset the event.

	Reset the event.
	\return TRUE for succeeded, FALSE for failed.
	*/
	BOOL Reset() noexcept
	{
		assert( IsValid() );
		return ::WSAResetEvent(m_hEvent);  //FALSE, ::WSAGetLastError()
	}
	/*! \brief Set the event.

	Set the event.
	\return TRUE for succeeded, FALSE for failed.
	*/
	BOOL Set() noexcept
	{
		assert( IsValid() );
		return ::WSASetEvent(m_hEvent);  //FALSE, ::WSAGetLastError()
	}

	/*! \brief Wait for WSA events.

	Wait for WSA events.
	\param cEvents [in] The number of event object handles in the array pointed to by lphEvents.
	                    It can be WSA_MAXIMUM_WAIT_EVENTS.
	\param lphEvents [in] A pointer to an array of event object handles.
	\param fWaitAll [in] A value that specifies the wait type.
	\param dwTimeout [in] The time-out interval, in milliseconds. It can be WSA_INFINITE.
	\return WSA_WAIT_*, see WSAWaitForMultipleEvents().
	        WSA_WAIT_EVENT_0 -> WSA_WAIT_EVENT_0 + cEvents - 1
	        WSA_WAIT_TIMEOUT
	        WSA_WAIT_FAILED ::WSAGetLastError()
	*/
	static DWORD Wait(DWORD cEvents, const WSAEVENT* lphEvents, BOOL fWaitAll, DWORD dwTimeout) noexcept
	{
		return ::WSAWaitForMultipleEvents(cEvents, lphEvents, fWaitAll, dwTimeout, FALSE);
	}

protected:
	WSAEVENT m_hEvent;  //!< WSA event handle
};

//_w_socket_addr

class _w_socket_addr
{
public:
	_w_socket_addr() noexcept : m_pInfo(NULL)
	{
	}
	_w_socket_addr(const _w_socket_addr&) = delete;
	_w_socket_addr(_w_socket_addr&& src) noexcept : m_pInfo(src.m_pInfo)
	{
		src.m_pInfo = NULL;
	}
	~_w_socket_addr() noexcept
	{
		Free();
	}

	_w_socket_addr& operator=(const _w_socket_addr&) = delete;
	_w_socket_addr& operator=(_w_socket_addr&& src) noexcept
	{
		if ( this != &src ) {
			if ( m_pInfo == src.m_pInfo ) {
				assert( m_pInfo == NULL );
			}
			else {
				Free();
				m_pInfo = src.m_pInfo;
				src.m_pInfo = NULL;
			}
		}
		return *this;
	}

	void Free() noexcept
	{
		if ( m_pInfo != NULL ) {
			::FreeAddrInfoExW(m_pInfo);
			m_pInfo = NULL;
		}
	}

	bool IsNull() const noexcept
	{
		return m_pInfo == NULL;
	}
	const ADDRINFOEXW* GetAddrInfo() const noexcept
	{
		return m_pInfo;
	}
	ADDRINFOEXW* GetAddrInfo() noexcept
	{
		return m_pInfo;
	}

	BOOL Fetch(PCWSTR pName, PCWSTR pServiceName) noexcept
	{
		assert( IsNull() );
		assert( pName != NULL );
		ADDRINFOEXW hints;
		::ZeroMemory(&hints, sizeof(ADDRINFOEXW));
		return ::GetAddrInfoExW(pName, pServiceName, NS_ALL, NULL, &hints, &m_pInfo,
					NULL, NULL, NULL, NULL) == NO_ERROR;  //FALSE, return value is WSA error code
	}

protected:
	ADDRINFOEXW* m_pInfo;
};

//_w_socket_helper

/*! \brief A helper class for socket.

A helper class for socket.
*/
class _w_socket_helper
{
public:
	/*! \brief Get host name.

	Get host name.
	\param lpSockAddr [in] A pointer to SOCKADDR.
	\param nSockAddrLen [in] The length, in bytes, of the structure pointed to by the lpSockAddr parameter.
	\param pNodeBuffer [out] A pointer to a buffer to hold the host name.
	\param dwNodeBufferSize [in] The number of WCHAR characters in the pNodeBuffer. User can use NI_MAXHOST.
	\param pServiceBuffer [out] A pointer to a buffer to hold the service name.
	\param dwServiceBufferSize [in] The number of WCHAR characters in the pServiceBuffer. User can use NI_MAXSERV.
	\param flags [in] A flag used to customize processing.
	\return TRUE for succeeded, FALSE for failed.
	*/
	static BOOL GetHostName(const SOCKADDR* lpSockAddr, socklen_t nSockAddrLen,
				PWCHAR pNodeBuffer, DWORD dwNodeBufferSize,
				PWCHAR pServiceBuffer, DWORD dwServiceBufferSize,
				INT flags = 0) noexcept
	{
		assert( lpSockAddr != NULL && nSockAddrLen > 0 );
		return ::GetNameInfoW(lpSockAddr, nSockAddrLen,
					pNodeBuffer, dwNodeBufferSize,
					pServiceBuffer, dwServiceBufferSize,
					flags) == 0;  //FALSE, ::WSAGetLastError()
	}
	/*! \brief Get IP string.

	Get IP string.
	\param lpSockAddr [in] A pointer to SOCKADDR.
	\param dwSockAddrLen [in] The length, in bytes, of the structure pointed to by the lpSockAddr parameter.
	\param lpszString [in, out] A pointer to the buffer that receives the string.
	\param lpdwStringLength [in, out] Specify the length of buffer in WCHARs.
	                                  Output the length of string including '\0'.
	\return TRUE for succeeded, FALSE for failed.
	        WSAEFAULT lpdwStringLength returns the required size.
	*/
	static BOOL GetIPName(SOCKADDR* lpSockAddr, DWORD dwSockAddrLen,
				LPWSTR lpszString, LPDWORD lpdwStringLength) noexcept
	{
		assert( lpSockAddr != NULL && dwSockAddrLen > 0 );
		return ::WSAAddressToStringW(lpSockAddr, dwSockAddrLen, NULL, lpszString, lpdwStringLength)
				== 0;  //SOCKET_ERROR, ::WSAGetLastError()
	}
	/*! \brief Get port number.

	Get port number.
	\param lpSockAddr [in] A pointer to SOCKADDR.
	\param nSockAddrLen [in] The length, in bytes, of the structure pointed to by the lpSockAddr parameter.
	\return The port number.
	*/
	static UINT GetPort(const SOCKADDR* lpSockAddr, socklen_t nSockAddrLen) noexcept
	{
		assert( lpSockAddr != NULL && nSockAddrLen > 0 );
		return ::ntohs(((LPSOCKADDR_IN)lpSockAddr)->sin_port);
	}
	/*! \brief Set port number.

	Set port number.
	\param nPort [in] Specify the port number.
	\param lpSockAddr [in, out] A pointer to SOCKADDR.
	\param nSockAddrLen [in] The length, in bytes, of the structure pointed to by the lpSockAddr parameter.
	*/
	static void SetPort(UINT nPort, SOCKADDR* lpSockAddr, socklen_t nSockAddrLen) noexcept
	{
		((LPSOCKADDR_IN)lpSockAddr)->sin_port = ::htons((u_short)nPort);
	}
	/*! \brief Get host name for local computer.

	Get host name for local computer.
	\param szName [out] Receive the host name.
	\param nNameLen [in] Specify the length of szName in WCHARs. It can be MAX_HOSTNAME_LEN + 4.
	\return TRUE for succeeded, FALSE for failed.
	        WSAEFAULT nNameLen is too small.
	*/
	static BOOL GetLocalHostName(PWSTR szName, int nNameLen) noexcept
	{
		assert( szName != NULL && nNameLen > 0 );
		return ::GetHostNameW(szName, nNameLen) == 0;  //SOCKET_ERROR, ::WSAGetLastError()
	}
	/*! \brief Fill a SOCKADDR structure.

	Fill a SOCKADDR structure.
	\param ssAddr [out] Specify the SOCKADDR* structure.
	\param nAddrLen [out] Receive the actual size of the structure.
	\param nAddressFamily [in] Specify the address family. See AF_*.
	\param nPort [in] Specify the port number. It can be 0.
	\param lpszAddress [in] A pointer to address string with IP format.
	                        If this parameter is NULL, ANY address will be set.
	\return TRUE for succeeded, FALSE for failed.
	*/
	static BOOL FillSockAddr(SOCKADDR_STORAGE& ssAddr, int& nAddrLen,
				int nAddressFamily, UINT nPort, LPWSTR lpszAddress = NULL) noexcept
	{
		nAddrLen = sizeof(SOCKADDR_STORAGE);
		::ZeroMemory(&ssAddr, nAddrLen);

		if ( lpszAddress != NULL ) {
			nAddrLen = nAddressFamily == AF_INET6 ? sizeof(SOCKADDR_IN6) : sizeof(SOCKADDR_IN);
			if ( ::WSAStringToAddressW(lpszAddress, nAddressFamily, NULL,
				(SOCKADDR*)&ssAddr, &nAddrLen) != 0 ) //SOCKET_ERROR, ::WSAGetLastError()
				return FALSE;
		}
		else {
			((SOCKADDR_IN*)&ssAddr)->sin_family = nAddressFamily;
			if ( nAddressFamily == AF_INET6 )
				((SOCKADDR_IN6*)&ssAddr)->sin6_addr = in6addr_any;
			else
				((SOCKADDR_IN*)&ssAddr)->sin_addr.s_addr = ::htonl(INADDR_ANY);
		}

		nAddrLen = nAddressFamily == AF_INET6 ? sizeof(SOCKADDR_IN6) : sizeof(SOCKADDR_IN);
		SetPort(nPort, (SOCKADDR*)&ssAddr, nAddrLen);
		return TRUE;
	}
};

//_w_async_socket

/*! \brief A class for asynchronization socket.

A class for asynchronization socket.
*/
class _w_async_socket
{
public:
	/*! \brief Constructor.

	Constructor.
	*/
	explicit _w_async_socket(SOCKET hSocket = INVALID_SOCKET) noexcept : m_hSocket(hSocket)
	{
	}
	_w_async_socket(const _w_async_socket&) = delete;
	_w_async_socket(_w_async_socket&& src) noexcept : m_hSocket(src.m_hSocket)
	{
		src.m_hSocket = INVALID_SOCKET;
	}
	/*! \brief Destructor.

	Destructor.
	*/
	~_w_async_socket() noexcept
	{
		Close();
	}

	_w_async_socket& operator=(const _w_async_socket&) = delete;
	_w_async_socket& operator=(_w_async_socket&& src) noexcept
	{
		if ( this != &src ) {
			if ( m_hSocket == src.m_hSocket ) {
				assert( m_hSocket == INVALID_SOCKET );
			}
			else {
				Close();
				m_hSocket = src.m_hSocket;
				src.m_hSocket = INVALID_SOCKET;
			}
		}
		return *this;
	}

	/*! \brief Close the socket.

	Close the socket.
	*/
	void Close() noexcept
	{
		if ( m_hSocket != INVALID_SOCKET ) {
			BOOL bRet = ::closesocket(m_hSocket) == 0;  //SOCKET_ERROR, ::WSAGetLastError()
			(void)bRet;
			assert( bRet );
			m_hSocket = INVALID_SOCKET;
		}
	}

	void GraceClose() noexcept
	{
		if ( IsValid() )
			Disable(SD_BOTH);  //no check
		Close();
	}

	/*! \brief Default cast operator.

	Default cast operator.
	*/
	operator SOCKET() const noexcept
	{
		return m_hSocket;
	}
	/*! \brief Check if the socket is valid.

	Check if the socket is valid.
	\return true for valid, false for invalid.
	*/
	bool IsValid() const noexcept
	{
		return m_hSocket != INVALID_SOCKET;
	}

	/*! \brief Attach a socket handle.

	Attach a socket handle.
	\param hSocket [in] A socket handle.
	*/
	void Attach(SOCKET hSocket) noexcept
	{
		if ( hSocket != m_hSocket ) {
			Close();
			m_hSocket = hSocket;
		}
	}
	/*! \brief Detach a socket handle.

	Detach a socket handle.
	\return A socket handle.
	*/
	SOCKET Detach() noexcept
	{
		SOCKET hSocket = m_hSocket;
		m_hSocket = INVALID_SOCKET;
		return hSocket;
	}

	/*! \brief Create a socket.

	Create a socket.
	\param af [in] The address family specification. See AF_*.
	\param st [in] The type specification for the new socket. See SOCK_*.
	\param proto [in] The protocol to be used. It can be 0, see IPPROTO_*.
	\param dwFlags [in] A flag that specifies the socket attribute.
	\return TRUE for succeeded, FALSE for failed.
	\note To create a raw socket, call Create(AF_INET, SOCK_RAW, IPPROTO_IP [or IPPROTO_RAW], 0),
	      set a BOOL variable bFlag to TRUE, then call SetSockOpt(IP_HDRINCL, &bFlag, sizeof(BOOL), IPPROTO_IP),
	      and call Bind() with local host address and specified port optionally.
	      If the raw socket is bound to an interface, use Read() and Write() to process data IO.
	      Otherwise, use ReadFrom() and WriteTo() to process data IO.
	      To receive all packets, set a DWORD variable dwValue to 1, and call Ioctl(SIO_RCVALL, &dwValue, sizeof(DWORD), ...).
	      If dwValue is set to 0, it means all packets would not be received.
	*/
	BOOL Create(int af, int st, int proto, DWORD dwFlags = WSA_FLAG_NO_HANDLE_INHERIT) noexcept
	{
		assert( !IsValid() );
		m_hSocket = ::WSASocketW(af, st, proto, NULL, 0, dwFlags | WSA_FLAG_OVERLAPPED);
		return m_hSocket != INVALID_SOCKET;  //FALSE, ::WSAGetLastError()
	}

	/*! \brief Get peer name.

	Get peer name on a connected socket.
	\param lpSockAddr [out] The SOCKADDR structure that receives the address of the peer.
	\param lpSockAddrLen [in, out] A pointer to the size, in bytes, of the lpSockAddr parameter.
	\return TRUE for succeeded, FALSE for failed.
	*/
	BOOL GetPeerName(SOCKADDR* lpSockAddr, int* lpSockAddrLen) const noexcept
	{
		assert( IsValid() );
		assert( lpSockAddr != NULL && lpSockAddrLen != NULL );
		return ::getpeername(m_hSocket, lpSockAddr, lpSockAddrLen) == 0;  //SOCKET_ERROR, ::WSAGetLastError()
	}
	/*! \brief Get socket name.

	Get socket name.
	\param lpSockAddr [out] The SOCKADDR structure that receives the address of the socket.
	\param lpSockAddrLen [in, out] A pointer to the size, in bytes, of the lpSockAddr parameter.
	\return TRUE for succeeded, FALSE for failed.
	*/
	BOOL GetSockName(SOCKADDR* lpSockAddr, int* lpSockAddrLen) const noexcept
	{
		assert( IsValid() );
		assert( lpSockAddr != NULL && lpSockAddrLen != NULL );
		return ::getsockname(m_hSocket, lpSockAddr, lpSockAddrLen) == 0;  //SOCKET_ERROR, ::WSAGetLastError()
	}

	/*! \brief Set socket option.

	Set socket option.
	\param nOptionName [in] The socket option for which the value is to be set (for example, SO_BROADCAST).
	\param lpOptionValue [in] A pointer to the buffer in which the value for the requested option is specified.
	\param nOptionLen [in] The size, in bytes, of the buffer pointed to by the lpOptionValue parameter.
	\param nLevel [in] The level at which the option is defined (for example, SOL_SOCKET).
	\return TRUE for succeeded, FALSE for failed.
	*/
	BOOL SetSockOpt(int nOptionName, const void* lpOptionValue, int nOptionLen,
				int nLevel = SOL_SOCKET) noexcept
	{
		assert( IsValid() );
		assert( lpOptionValue != NULL );
		return ::setsockopt(m_hSocket, nLevel, nOptionName, (LPCSTR)lpOptionValue, nOptionLen) == 0;  //SOCKET_ERROR, ::WSAGetLastError()
	}
	/*! \brief Get socket option.

	Get socket option.
	\param nOptionName [in] The socket option for which the value is to be retrieved. Example: SO_ACCEPTCONN.
	\param lpOptionValue [out] A pointer to the buffer in which the value for the requested option is to be returned.
	\param lpnOptionLen [in, out] A pointer to the size, in bytes, of the lpOptionValue buffer.
	\param nLevel [in] The level at which the option is defined (for example, SOL_SOCKET).
	\return TRUE for succeeded, FALSE for failed.
	*/
	BOOL GetSockOpt(int nOptionName, void* lpOptionValue, int* lpOptionLen,
				int nLevel = SOL_SOCKET) const noexcept
	{
		assert( IsValid() );
		assert( lpOptionValue != NULL && lpOptionLen != NULL );
		return ::getsockopt(m_hSocket, nLevel, nOptionName, (LPSTR)lpOptionValue, lpOptionLen) == 0;  //SOCKET_ERROR, ::WSAGetLastError()
	}

	/*! \brief Disable socket.

	Disable socket.
	\param nFlag [in] A flag that describes what types of operation will no longer be allowed. See SD_*.
	\return TRUE for succeeded, FALSE for failed.
	\note Call this method before calling Close().
	*/
	BOOL Disable(int nFlag) noexcept
	{
		assert( IsValid() );
		return ::shutdown(m_hSocket, nFlag) == 0;  //SOCKET_ERROR, ::WSAGetLastError()
	}

	/*! \brief Select events.

	Select events.
	\param hEventObject [in] Handle identifying the event object to be associated with
	                         the specified set of FD_XXX network events.
	\param lNetworkEvents [in] Bitmask that specifies the combination of FD_XXX network events
	                           in which the application has interest.
	\return TRUE for succeeded, FALSE for failed.
	\note This method automatically sets a socket to non-blocking mode. To set the socket back to
	      blocking mode, an application must first disable WSAEventSelect by calling WSAEventSelect (this method)
	      with the lNetworkEvents parameter equal to zero, and calls WSAIoctl with FIONBIO.
	*/
	BOOL SelectEvents(WSAEVENT hEventObject, LONG lNetworkEvents) noexcept
	{
		assert( IsValid() );
		return ::WSAEventSelect(m_hSocket, hEventObject, lNetworkEvents) == 0;  //SOCKET_ERROR, ::WSAGetLastError()
	}
	/*! \brief Get network event states.

	Get network event states.
	\param hEventObject [in] An optional handle identifying an associated event object to be reset.
	                         It can be NULL.
	\param lpNetworkEvents [out] A pointer to a WSANETWORKEVENTS structure that is filled with
	                             a record of network events that occurred and any associated error codes.
	\return TRUE for succeeded, FALSE for failed.
	\note Use this method after the event signaled (WSAWaitForMultipleEvents). Use (lpNetworkEvents->lNetworkEvents & FD_XXXX)
	      to check the event flag. Use FD_XXXX_BIT to see error code in lpNetworkEvents->iErrorCode[FD_MAX_EVENTS],
	      and convert it to HRESULT by HRESULT_FROM_WIN32() if it is not zero.
	*/
	BOOL EnumNetworkEvents(WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents) noexcept
	{
		assert( IsValid() );
		assert( lpNetworkEvents != NULL );
		::ZeroMemory(lpNetworkEvents, sizeof(WSANETWORKEVENTS));
		return ::WSAEnumNetworkEvents(m_hSocket, hEventObject, lpNetworkEvents) == 0;  //SOCKET_ERROR, ::WSAGetLastError()
	}

	/*! \brief Bind a socket.

	Bind a socket.
	\param lpSockAddr [in] Address to assign to the socket from the sockaddr structure.
	\param nSockAddrLen [in] Length of the value in the lpSockAddr parameter, in bytes.
	\return TRUE for succeeded, FALSE for failed.
	*/
	BOOL Bind(const SOCKADDR* lpSockAddr, int nSockAddrLen) noexcept
	{
		assert( IsValid() );
		assert( lpSockAddr != NULL && nSockAddrLen > 0 );
		return ::bind(m_hSocket, lpSockAddr, nSockAddrLen) == 0;  //SOCKET_ERROR, ::WSAGetLastError()
	}
	/*! \brief Listen state.

	Listen state.
	\param nConnectionBacklog [in] The maximum length of the queue of pending connections.
	                               The range of this value can be [1, 5], or SOMAXCONN.
	\return TRUE for succeeded, FALSE for failed.
	*/
	BOOL Listen(int nConnectionBacklog = SOMAXCONN) noexcept
	{
		assert( IsValid() );
		return ::listen(m_hSocket, nConnectionBacklog) == 0;  //SOCKET_ERROR, ::WSAGetLastError()
	}
	/*! \brief Accept incoming connection.

	Accept incoming connection.
	\param sAcceptSocket [in] A socket on which to accept an incoming connection.
	\param lpOutputBuffer [in] A pointer to a buffer that receives the first block of data.
	                           This cannot be NULL. The size of buffer is at least 32 (16 + 16) bytes.
	\param lpOverlapped [in] A pointer to OVERLAPPED. It cannot be NULL.
	\return TRUE for succeeded, FALSE for failed.
	        ERROR_IO_PENDING The operation is still in progress.
	*/
	BOOL Accept(SOCKET sAcceptSocket, PVOID lpOutputBuffer, LPOVERLAPPED lpOverlapped) noexcept
	{
		assert( IsValid() );
		assert( lpOutputBuffer != NULL && lpOverlapped != NULL && sAcceptSocket != INVALID_SOCKET );
		return ::AcceptEx(m_hSocket, sAcceptSocket, lpOutputBuffer, 0,
					16 + sizeof(SOCKADDR_STORAGE), 16 + sizeof(SOCKADDR_STORAGE),
					NULL, lpOverlapped);  //FALSE, ::WSAGetLastError()
	}
	/*! \brief Connect other socket application.

	Connect other socket application.
	\param lpSockAddr [in] A pointer to a sockaddr structure that specifies the address to which to connect.
	\param nSockAddrLen [in] The length, in bytes, of the sockaddr structure pointed to by the lpSockAddr parameter.
	\return TRUE for succeeded, FALSE for failed.
	        WSAEWOULDBLOCK if the socket is pending in nonblocking mode.
	\note Use SelectEvents() with FD_CONNECT before calling this method and use WSAEvent::Wait
	      to check event for connection completion. Use EnumNetworkEvents() to see the connection result.
	*/
	BOOL Connect(const SOCKADDR* lpSockAddr, int nSockAddrLen) noexcept
	{
		assert( IsValid() );
		assert( lpSockAddr != NULL && nSockAddrLen > 0 );
		return ::WSAConnect(m_hSocket, lpSockAddr, nSockAddrLen, NULL, NULL, NULL, NULL) == 0;  //SOCKET_ERROR, ::WSAGetLastError()
	}

	/*! \brief Begin a read operation.

	Begin a read operation.
	\param lpBuf [out] Receive the data.
	\param nBufLen [in] The bytes of lpBuf.
	\param lpRecv [out] A pointer to the number of data received in bytes.
	                    It should be NULL if lpOverlapped is not NULL.
	\param lpOverlapped [in] A pointer to a WSAOVERLAPPED structure.
	                         It can be NULL for non-ovelapped operation to use blocking or non-blocking mode.
	\param dwFlags [in] A pointer to flags used to modify the behavior of the WSARecv function call.
	\return TRUE for succeeded, FALSE for failed.
	        *lpRecv == 0 (return value is TRUE) means the remote socket is closed.
	        WSA_IO_PENDING means the operation is pending.
	        WSAEDISCON means the virtual circuit was gracefully closed by the remote side.
	        WSAEWOULDBLOCK means the operation is still in progress for non-overlapped, non-blocking socket.
	*/
	BOOL Read(void* lpBuf, ULONG nBufLen, LPDWORD lpRecv, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags = 0) noexcept
	{
		assert( IsValid() );
		assert( lpBuf != NULL && nBufLen > 0);

		WSABUF buff;
		buff.buf = (char*)lpBuf;
		buff.len = nBufLen;
		DWORD flags = dwFlags;

		return ::WSARecv(m_hSocket, &buff, 1, lpRecv, &flags, lpOverlapped, NULL) == 0;  //SOCKET_ERROR, ::WSAGetLastError()
	}
	/*! \brief Begin a read operation for datagram.

	Begin a read operation for datagram.
	\param lpBuf [out] Receive the data.
	\param nBufLen [in] The bytes of lpBuf.
	\param lpRecv [out] A pointer to the number of data received in bytes.
	                    It should be NULL if lpOverlapped is not NULL.
	\param lpSockAddr [out] An optional pointer to a buffer that will hold the source address
	                        upon the completion of the overlapped operation.
	                        This cannot be a stack-based variable.
	\param lpSockAddrLen [in, out] A pointer to the size, in bytes, of the "from" buffer required
	                               only if lpSockAddr is specified.
	                               This cannot be a stack-based variable.
	\param lpOverlapped [in] A pointer to a WSAOVERLAPPED structure.
	                         It can be NULL for nonovelapped sockets.
	\param dwFlags [in] A pointer to flags used to modify the behavior of the WSARecvFrom function call.
	\return TRUE for succeeded, FALSE for failed.
	        WSA_IO_PENDING means the operation is pending.
	        WSAEWOULDBLOCK means the operation is still in progress for non-overlapped, non-blocking socket.
	*/
	BOOL ReadFrom(void* lpBuf, ULONG nBufLen, LPDWORD lpRecv,
				SOCKADDR* lpSockAddr, int* lpSockAddrLen,
				LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags = 0) noexcept
	{
		assert( IsValid() );
		assert( lpBuf != NULL && nBufLen > 0
			&& lpSockAddr != NULL && lpSockAddrLen != NULL );

		WSABUF buff;
		buff.buf = (char*)lpBuf;
		buff.len = nBufLen;
		DWORD flags = dwFlags;

		return ::WSARecvFrom(m_hSocket, &buff, 1, lpRecv, &flags, lpSockAddr, lpSockAddrLen, lpOverlapped, NULL) == 0;  //SOCKET_ERROR, ::WSAGetLastError()
	}
	/*! \brief Begin a write operation.

	Begin a write operation.
	\param lpBuf [in] A pointer to send buffer.
	\param nBufLen [in] The bytes of lpBuf.
	\param lpSent [out] A pointer to the number of data sent in bytes.
	                    It should be NULL if lpOverlapped is not NULL.
	\param lpOverlapped [in] A pointer to a WSAOVERLAPPED structure.
	                         It can be NULL for nonovelapped sockets.
	\param dwFlags [in] The flags used to modify the behavior of the WSASend function call.
	\return TRUE for succeeded, FALSE for failed.
	        WSA_IO_PENDING means the operation is pending.
	        WSAEWOULDBLOCK means the operation is still in progress for non-overlapped, non-blocking socket.
	*/
	BOOL Write(const void* lpBuf, ULONG nBufLen, LPDWORD lpSent,
				LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags = 0) noexcept
	{
		assert( IsValid() );
		assert( lpBuf != NULL && nBufLen > 0 );

		WSABUF buff;
		buff.buf = (char*)lpBuf;
		buff.len = nBufLen;

		return ::WSASend(m_hSocket, &buff, 1, lpSent, dwFlags, lpOverlapped, NULL) == 0;  //SOCKET_ERROR, ::WSAGetLastError()
	}
	/*! \brief Begin a write operation for datagram.

	Begin a write operation for datagram.
	\param lpBuf [in] A pointer to send buffer.
	\param nBufLen [in] The bytes of lpBuf.
	\param lpSent [out] A pointer to the number of data sent in bytes.
	                    It should be NULL if lpOverlapped is not NULL.
	\param lpSockAddr [in] An optional pointer to the address of the target socket in the SOCKADDR structure.
	\param nSockAddrLen [in] The size, in bytes, of the address in the lpSockAddr parameter.
	\param lpOverlapped [in] A pointer to a WSAOVERLAPPED structure.
	                         It can be NULL for nonovelapped sockets.
	\param dwFlags [in] The flags used to modify the behavior of the WSASendTo function call.
	\return TRUE for succeeded, FALSE for failed.
	        WSA_IO_PENDING means the operation is pending.
	        WSAEWOULDBLOCK means the operation is still in progress for non-overlapped, non-blocking socket.
	*/
	BOOL WriteTo(const void* lpBuf, ULONG nBufLen, LPDWORD lpSent,
				const SOCKADDR* lpSockAddr, int nSockAddrLen,
				LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags = 0) noexcept
	{
		assert( IsValid() );
		assert( lpBuf != NULL && nBufLen > 0
			&& lpSockAddr != NULL && nSockAddrLen > 0 );

		WSABUF buff;
		buff.buf = (char*)lpBuf;
		buff.len = nBufLen;

		return ::WSASendTo(m_hSocket, &buff, 1, lpSent, dwFlags, lpSockAddr, nSockAddrLen, lpOverlapped, NULL) == 0;  //SOCKET_ERROR, ::WSAGetLastError()
	}
	/*! \brief Control the mode of a socket.

	Control the mode of a socket.
	\param dwIoControlCode [in] The control code of operation to perform.
	\param lpvInBuffer [in] A pointer to the input buffer.
	\param cbInBuffer [in] The size, in bytes, of the input buffer.
	\param lpvOutBuffer [out] A pointer to the output buffer.
	\param cbOutBuffer [in] The size, in bytes, of the output buffer.
	\param lpcbBytesReturned [out] A pointer to actual number of bytes of output.
	\param lpOverlapped [in] A pointer to a WSAOVERLAPPED structure.
	                         It can be NULL for nonovelapped sockets.
	\return TRUE for succeeded, FALSE for failed.
	        WSA_IO_PENDING means the operation is pending.
	        WSAEWOULDBLOCK means the operation is still in progress for non-overlapped, non-blocking socket.
	*/
	BOOL Ioctl(DWORD dwIoControlCode, LPVOID lpvInBuffer, DWORD cbInBuffer,
				LPVOID lpvOutBuffer, DWORD cbOutBuffer, LPDWORD lpcbBytesReturned,
				LPWSAOVERLAPPED lpOverlapped) noexcept
	{
		assert( IsValid() );
		return ::WSAIoctl(m_hSocket, dwIoControlCode, lpvInBuffer, cbInBuffer, lpvOutBuffer, cbOutBuffer,
					lpcbBytesReturned, lpOverlapped, NULL) == 0;  //SOCKET_ERROR, ::WSAGetLastError()
	}
	/*! \brief Get overlapped result.

	Get overlapped result.
	\param lpOverlapped [in] A pointer to a WSAOVERLAPPED structure that was specified
	                         when the overlapped operation was started.
	\param lpcbTransfer [out] A pointer to a 32-bit variable that receives the number of bytes
	                          that were actually transferred by a send or receive operation, or by WSAIoctl.
	\param fWait [in] A flag that specifies whether the function should wait for the pending
	                  overlapped operation to complete.
	\param lpdwFlags [out] A pointer to a 32-bit variable that will receive one or more flags
	                       that supplement the completion status.
	                       This parameter will contain the result value of lpFlags in WSARecv and WSARecvFrom.
	\return TRUE for succeeded, FALSE for failed.
	        WSA_IO_INCOMPLETE means the operation is still pending.
	*/
	BOOL GetOverlappedResult(LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer, BOOL fWait,
				LPDWORD lpdwFlags) noexcept
	{
		assert( IsValid() );
		assert( lpOverlapped != NULL && lpcbTransfer != NULL && lpdwFlags != NULL );
		return ::WSAGetOverlappedResult(m_hSocket, lpOverlapped, lpcbTransfer, fWait, lpdwFlags);  //FALSE, ::WSAGetLastError()
	}

protected:
	SOCKET m_hSocket;   //!< socket handle
};

//------------------------------------------------------------------------------
//tools

inline
bool host_to_sockaddr(const WCHAR* host, UINT port,
			SOCKADDR_STORAGE& ss, int& len) noexcept
{
	WCHAR* szHost = ::_wcsdup(host);
	if ( szHost == NULL )
		return false;
	if ( !_w_socket_helper::FillSockAddr(ss, len, AF_INET, port, szHost) ) {
		::free(szHost);
		_w_socket_addr sa;
		WCHAR szPort[64] = { 0 };
		::_snwprintf_s(szPort, sizeof(szPort) / sizeof(WCHAR), _TRUNCATE, L"%u", port);
		if ( !sa.Fetch(host, szPort) )
			return false;
		const ADDRINFOEXW* pInfo = sa.GetAddrInfo();
		while ( pInfo != NULL ) {
			if ( pInfo->ai_family == AF_INET ) {
				::memcpy(&ss, pInfo->ai_addr, pInfo->ai_addrlen);
				len = (int)pInfo->ai_addrlen;
				return true;
			}
			pInfo = pInfo->ai_next;
		}
		return false;
	}
	::free(szHost);
	return true;
}

////////////////////////////////////////////////////////////////////////////////
